#include "miniopt.h"
#include "minicon.h"

//=======================================================================================

enum Etools
{ eGnu      = 0
, eTrue     = 1,  eFalse    = 2,  eEcho     = 3
, eMkdir    = 4,  eCp       = 5,  eMv       = 6,  eRm       = 7, eTouch = 8
, eSleep    = 9,  eUuidgen  = 10, eWho      = 11, eUptime   = 12
, eTr       = 13, eCmp      = 14
, eLn       = 15
, eBasename = 16, eDirname  = 17, ePathname = 18, ePwd  = 19
, eStreams  = 20, eCat      = 21
};

static int mode = eGnu;
static int v_opt=0, p_opt=0, i_opt=0, u_opt=0, f_opt=0, n_opt=0,e_opt=0,E_opt=0, l_opt=0;
static int a_opt=0, c_opt=0, m_opt=0, r_opt=0, s_opt=0, d_opt=0,g_opt=0,x_opt=0;

//=======================================================================================

static const char h_gnu[] = "[Command] [command_parameters]\r\n"
"  \1e --{help|version" /*"|link"*/ "}\0"
"Execute GNU commands\0"
"Commands:\r\n"
"  \1m\r\n"
"\r\n"
"Examples:\r\n"
"  \1e --help\r\n"
"  \1e cp --help\r\n"
"  \1e mkdir -pv Dir1 Dir2 Dir3/a/b/c\r\n"
;

static const char common_opt_help[] =
"  --help            display this help and exit\r\n"
"  --version         output version information and exit\r\n"
"  --cp=[Ci,]Co[,Ce] set [Input,]OutPut[,Error] code page(s)\r\n"
"\r\n"
"Examples:\r\n"
;

static Opt::option o_gnu[] = { { '\0', "link",   &l_opt, NULL } };

static int do_gnu( WCHAR **args, int na );

//---------------------------------------------------------------------------------------

static const char h_true[] = "[--help|--version]\0"
                             "Always returns SUCCESS.\0";

static int do_true( WCHAR **args, int na )
{ (void)args, na; return 0; }

static const char h_false[] = "[--help|--version]\0"
                             "Always returns UNSUCCESS.\0";

static int do_false( WCHAR **args, int na )
{ (void)args, na; return -1; } // will be converted to +1...

//---------------------------------------------------------------------------------------

static const char h_echo[] = "[Options] [String...]\0"
"Echo the String(s) to standard output.\0"
"Options:\r\n"
"  -n                do not output the trailing newline\r\n"
"  -e                enable interpretation of backslash escapes\r\n"
"  -E                disable interpretation of backslash escapes (default)\r\n"
"\1c"
"  \1e Some text\r\n"
"  \1e -n No newline\r\n"
"  \1e -e \"  Bell\\a no newline\\c\"\r\n"
"\r\n"
"If -e is in effect, the following sequences are recognized:\r\n"
"  \\0NNN  the character whose ASCII code is NNN (octal)\r\n"
"  \\\\     backslash\r\n"
"  \\a     alert (BEL)\r\n"
"  \\b     backspace\r\n"
"  \\c     suppress trailing newline\r\n"
"  \\f     form feed\r\n"
"  \\n     new line\r\n"
"  \\r     carriage return\r\n"
"  \\t     horizontal tab\r\n"
"  \\v     vertical tab\r\n"
"  \\PQOALGUISE (%\"'&<>^| =)\r\n"
;

static Opt::option o_echo[] =
{ { 'n', NULL, &n_opt, NULL }
, { 'e', NULL, &e_opt, NULL }
, { 'E', NULL, &E_opt, NULL }
};

extern int echo( WCHAR **args, int na, bool escape, bool newline );

static int do_echo( WCHAR **args, int na )
{
   return echo(args, na, e_opt > E_opt, n_opt <= 0);
}

//---------------------------------------------------------------------------------------

static const char h_mkdir[] = "[Options] Directory...\0"
"Create the Directory(ies) if they do not already exist.\0"
"Options:\r\n"
"  -p, --parents     no error if existing, make parent directories as needed\r\n"
"  -v, --verbose     print a message for each created directory\r\n"
"\1c"
"  \1e -p dir1\r\n"
"  \1e -v --parents a/b/c/d x/y\r\n"
;

static Opt::option o_mkdir[] =
{ { 'v', "verbose", &v_opt, NULL }
, { 'p', "parents", &p_opt, NULL }
};

extern int mkdir( WCHAR **args, int na, bool parents, bool verbose );

static int do_mkdir( WCHAR **args, int na )
{
   return mkdir(args, na, p_opt > 0, v_opt > 0);
}

//---------------------------------------------------------------------------------------

static const char h_cp[] = "[Options] Source Dest\r\n"
                     "  \1e [Options] Source ... Directory\0"
"Copy Source to Dest, or multiple Source(s) to Directory\0"
"Options:\r\n"
"  -p, --preserve    preserve time stamps and access rights\r\n"
"  -f, --force       do not prompt before overwriting\r\n"
"  -i, --interactive ask before overwriting existing file\r\n"
"  -u, --update      copy only newer or non existing file(s)\r\n"
"  -r, --recursive   recursive copy directory contents\r\n"
"  -v, --verbose     show copied file(s)\r\n"
"\1c"
"  \1e -i file1 file2\r\n"
"  \1e -pu --verbose file1 file2 file3 dstdir\r\n"
;

int cp_mv(const char *op, WCHAR **args,int na, int v,bool p,int f,bool i,bool u,bool r);

static Opt::option o_cp[] =
{ { 'v', "verbose",     &v_opt, NULL }
, { 'p', "preserve",    &p_opt, NULL }
, { 'f', "force",       &f_opt, NULL }
, { 'F', NULL,          &x_opt, NULL } // secret option :)
, { 'i', "interactive", &i_opt, NULL }
, { 'u', "update",      &u_opt, NULL }
, { 'r', "recursive",   &r_opt, NULL }
, { 'R', NULL,          &r_opt, NULL }
};

static int do_cp( WCHAR **args, int na )
{
   return cp_mv("copy", args,na, v_opt, p_opt>0, f_opt+x_opt*2, i_opt>0, u_opt>0, r_opt>0);
}

//---------------------------------------------------------------------------------------

static const char h_mv[] = "[Options] Source Dest\r\n"
"  \1e [Options] Source ... Directory\0"
"Rename Source to Dest, or move Source(s) to Directory\0"
"Options:\r\n"
"  -f, --force       do not prompt before overwriting\r\n"
"  -i, --interactive ask before overwriting existing file\r\n"
"  -u, --update      move only newer or non existing file(s)\r\n"
"  -v, --verbose     show moved file(s)\r\n"
"\1c"
"  \1e -f file1 file2\r\n"
"  \1e -pu --verbose *.cpp *.h dstdir\r\n"
;

static Opt::option o_mv[] =
{ { 'v', "verbose",     &v_opt, NULL }
, { 'f', "force",       &f_opt, NULL }
, { 'i', "interactive", &i_opt, NULL }
, { 'u', "update",      &u_opt, NULL }
};

static int do_mv( WCHAR **args, int na )
{
   return cp_mv("move", args,na, v_opt, true, f_opt > 0, i_opt > 0, u_opt > 0, false);
}

//---------------------------------------------------------------------------------------

static const char h_ln[] = "[Options] Target Link\r\n"
"  \1e [Options] Target ... Directory\r\n"
"  \1e --real [--verbose] [--force] Path ...\0"
"Create Link to Target, or multiple link(s) to Target(s) in Directory\0"
"Options:\r\n"
"  -f, --force       do not prompt before overwriting (-rv: find hardlinks)\r\n"
"  -i, --interactive ask before overwriting existing file\r\n"
"  -h, --hard        create hard link (default is file/hard, dir/junction)\r\n"
"  -s, --symbolic    create symbolic link (or volume mount point)\r\n"
"  -v, --verbose     show each linked pair(s) (-r: extra info)\r\n"
"  -r, --real        show real pathname(s) -- resolve link(s)\r\n"
"\1c"
"  \1e -h --verbose file1 file2\r\n"
"  \1e -f file1 file2.* linked_dir link_directory\r\n"
"  \1e -rvf file1 C:\\*.* dir3\r\n"
;

int link(char mode, WCHAR **args,int na, int v,bool f,bool i,int l_type);

static Opt::option o_ln[] =
{ { 'v', "verbose",     &v_opt, NULL }
, { 'f', "force",       &f_opt, NULL }
, { 'i', "interactive", &i_opt, NULL }
, { 'h', "hard",        &x_opt, NULL }
, { 's', "symbolic",    &s_opt, NULL }
, { 'r', "real",        &r_opt, NULL }
};

static int do_ln( WCHAR **args, int na )
{
   char mode = 'l';
   if (r_opt > 0) mode = 'r';
   return link(mode, args,na, v_opt, f_opt>0, i_opt>0, s_opt-x_opt);
}

//---------------------------------------------------------------------------------------

static const char h_rm[] = "[Options] File_or_Directory ...\0"
"Remove specified File(s) or/and Directory(ies)\0"
"Options:\r\n"
"  -f, --force       ignore nonexistent files, never prompt\r\n"
"  -i, --interactive prompt before any removal\r\n"
"  -r, --recursive   remove the contents of directories recursively\r\n"
"  -v, --verbose     explain what is being done\r\n"
"\1c"
"  \1e -f file1 file2 *.bak\r\n"
"  \1e --verbose --recursive dir1 dir2\r\n"
;

int rm(WCHAR ** args,int na, bool verbose, bool force, bool interactive, bool recursive);

static Opt::option o_rm[] =
{ { 'v', "verbose",     &v_opt, NULL }
, { 'f', "force",       &f_opt, NULL }
, { 'i', "interactive", &i_opt, NULL }
, { 'r', "recursive",   &r_opt, NULL }
, { 'R', NULL,          &r_opt, NULL }
};

static int do_rm( WCHAR **args, int na )
{
   return rm(args,na, v_opt > 0, f_opt > 0, i_opt > 0 && f_opt <= 0, r_opt > 0);
}

//---------------------------------------------------------------------------------------

static const char h_touch[] = "[Options] File ...\0"
"Update the access and modification times of each File.\0"
"Options:\r\n"
"  -a                change only the access time\r\n"
"  -c, --no-create   do not create any files\r\n"
"  -m                change only the modification time\r\n"
"  -r, --reference=F use this File's times instead of current time\r\n"
"  -t Stamp          use [[CC]YY]MMDDhhmm[.ss] instead of current time\r\n"
"  -A Adjust         add [-][[hh]mm]SS[.sss] seconds to Current/File/Stamp time\r\n"
"\1c"
"  \1e file1 file2 file3\r\n"
"  \1e -m --no-create -r orig_file target_file\r\n"
"  \1e -act 11131632 file\r\n"
"  \1e -r t1.file -A -5 t2.file\r\n"
;

static const WCHAR *pw_tim = NULL, *pw_ref = NULL, *pw_adj = NULL;

static int f_ref( const WCHAR *s )
{
   if ( !s || !*s ) return Opt::Err_Missed_Value;
   pw_tim = NULL;
   pw_ref = s;
   return 0;
}

static int f_tim( const WCHAR *s )
{
   if ( !s || !*s ) return Opt::Err_Missed_Value;
   pw_ref = NULL;
   pw_tim = s;
   return 0;
}

static int f_adj( const WCHAR *s )
{
   if ( !s || !*s ) return Opt::Err_Missed_Value;
   pw_adj = s;
   return 0;
}

static Opt::option o_touch[] =
{ { 'a', NULL,          &a_opt, NULL  }
, { 'c', "no_create",   &c_opt, NULL  }
, { 'm', NULL,          &m_opt, NULL  }
, { 'r', "reference",   NULL,   f_ref }
, { 't', NULL,          NULL,   f_tim }
, { 'A', NULL,          NULL,   f_adj }
};

extern int touch(WCHAR **args, int na,
   bool aonly, bool monly, bool nocreate, char tmode, const WCHAR *pt, const WCHAR *pa);

static int do_touch( WCHAR **args, int na )
{
   char mode = '\0'; const WCHAR *pt = NULL;
   if ( pw_tim ) { mode = 't'; pt = pw_tim; }
   if ( pw_ref ) { mode = 'r'; pt = pw_ref; }
   return touch(args,na, a_opt > m_opt, m_opt > a_opt, c_opt > 0, mode,pt, pw_adj);
}

//---------------------------------------------------------------------------------------

static const char h_uuidgen[] = "[Options]\0"
"Generate UUID/GUID and output it in various formats.\0"
"Options:\r\n"
"  -x                generate sequential (V1) UUIDs\r\n"
"  -nN               number of UUIDs to generate\r\n"
"  -i                output UUID in an IDL interface template\r\n"
"  -s                output UUID as an initialized C struct\r\n"
"  -c                Output UUID in upper case\r\n"
"  -ig               output UUID as an GUID IMPLEMENT_OLECREATE\r\n"
"  -sg               output UUID as an GUID static C struct\r\n"
"  -d[g]             output UUID as an DEFINE_GUID\r\n"
"  -r[g]             output UUID as an GUID registry record\r\n"
"\1c"
"  \1e\r\n"
"  \1e -sn 5 -c\r\n"
"  \1e -i -d\r\n"
"  \1e -r -x -n 20\r\n"
;

static unsigned nN = 1;
extern bool getUN( const WCHAR *pw, unsigned& ui ); // from touch.cpp

static int f_num( const WCHAR *s )
{
   if ( !s || !*s ) return Opt::Err_Missed_Value;
   if ( !getUN(s, nN) || nN < 1) return Opt::Err_Wrong_Value;
   return 0;
}

static Opt::option o_uuidgen[] =
{ { 'x', NULL, &x_opt, NULL  }
, { 's', NULL, &s_opt, NULL  }
, { 'i', NULL, &i_opt, NULL  }
, { 'c', NULL, &c_opt, NULL  }
, { 'g', NULL, &g_opt, NULL  }
, { 'd', NULL, &d_opt, NULL  }
, { 'r', NULL, &r_opt, NULL  }
, { 'n', NULL, NULL,   f_num }
};

extern int uuidgen(bool seq, unsigned n, int of, bool ucase);

static int do_uuidgen( WCHAR **args, int na )
{
   (void)args;
   if ( na > 0 ) return Opt::opt_err(Opt::Err_TooMany_Params);
   int of = '0';
   if ( i_opt > 0 ) of = g_opt > 0 ? 'I' : 'i';
   if ( s_opt > 0 ) of = g_opt > 0 ? 'S' : 's';
   if ( d_opt > 0 ) of = 'D'; //g_opt > 0 ? 'D' : 'd';
   if ( r_opt > 0 ) of = 'R'; //g_opt > 0 ? 'R' : 'r';
   return uuidgen(x_opt > 0, nN, of, c_opt > 0);
}

//---------------------------------------------------------------------------------------

static const char h_sleep[] = "[-v] time\0"
"Sleep for specified time period.\0"
"Options:\r\n"
"  -v, --verbose     shows sleeping time\r\n"
"\1c"
"  \1e 10      -- 10 sec\r\n"
"  \1e -v 10   -- 10 sec, verbose\r\n"
"  \1e 7.25    -- 7 sec 250 ms\r\n"
"  \1e 1:15:00 -- 1 hour 15 minutes\r\n"
;

extern bool getMS( const WCHAR *pw, DWORD& ms ); // from touch.cpp
int err_ret(int errcode, const char *msg, const WCHAR *name); // below

static Opt::option o_sleep[] =
{ { 'v', "verbose", &v_opt, NULL  }
};

static int do_sleep( WCHAR **args, int na )
{
   DWORD msec = 0;
   if ( na < 1 ) return Opt::opt_err(Opt::Err_Missed_Param);
   if ( na > 1 ) return Opt::opt_err(Opt::Err_TooMany_Params);
   if ( !getMS(args[0], msec) ) {
      ::SetLastError(0);
      return err_ret(-20, "Bad time value:", args[0]);
   }
   if ( v_opt > 0 ) Con::f("sleep for %u.%03u seconds ... ", msec/1000, msec % 1000);
   ::Sleep(msec);
   if ( v_opt > 0 ) Con::n();
   return 0;
}

//---------------------------------------------------------------------------------------

static const char h_uptime[] = "[-b]\0"
"Show system uptime (o boot time).\0"
"Options:\r\n"
"  -b, --boot        shows system boot time\r\n"
"\1c"
"  \1e\r\n"
"  \1e -b\r\n"
;

static const char h_who[] = "[-b] [-v]\0"
"Enumerate active (logged-in) users.\0"
"Options:\r\n"
"  -v, --verbose     shows verbose user list\r\n"
"  -b, --boot        shows system boot time\r\n"
"\1c"
"  \1e\r\n"
"  \1e -b\r\n"
"  \1e -v\r\n"
;

extern int uptime( bool boottime );
extern int who( bool verbose );

static Opt::option o_uptime[] =
{ { 'b', "boot", &a_opt, NULL }
};
static Opt::option o_who[] =
{ { 'b', "boot", &a_opt, NULL }
, { 's', "short",&s_opt, NULL }
, { 'v', "short",&v_opt, NULL }
, { 'u', "user",  NULL,  NULL }
};

static int do_uptime( WCHAR **args, int na )
{
   (void)args, na;
   return uptime( a_opt > 0 );
}

static int do_who( WCHAR **args, int na )
{
   (void)args, na;
   if ( a_opt > 0 ) return uptime(true);
   else return who(v_opt > s_opt);
}

//---------------------------------------------------------------------------------------

static const char h_tr[] = "[Options] Set1 [Set2]\0"
"Translate, squeeze, and/or delete characters from input, writing to output.\r\n"
"By default standard input/output used.\0"
"Options:\r\n"
"  -c, --complement  first complement Set1\r\n"
"  -d, --delete      delete characters in Set1, do not translate\r\n"
"  -s, --squeeze-repeats   replace sequence of characters with one\r\n"
"  -i input_file     specify input file\r\n"
"  -o output_file    specify output file\r\n"
"\1c"
"  \1e AaBb BbAa\r\n"
"  \1e -d \\r < f.in > f.out\r\n"
"  \1e -cs [:alnum:] \\n -i f.in\r\n"
"  \1e [0-9][:upper:][:lower:] [9-0][:lower:][:upper:] -io f.i f.o\r\n"
"\r\n"
"Sets are specified as strings of characters. Most represent themselves.\r\n"
"Interpreted sequences are:\r\n"
"\r\n"
"\\OOO          character with octal value OOO\r\n"
"\\xHH          character with hex value 0xHH\r\n"
"\\uHHHH        Unicode character with hex value 0xHHHH\r\n"
"\\\\            backslash\r\n"
"\\a            audible BEL\r\n"
"\\b            backspace\r\n"
"\\f            form feed\r\n"
"\\n            new line\r\n"
"\\r            return\r\n"
"\\t            horizontal tab\r\n"
"\\v            vertical tab\r\n"
"\\PQOALGUISE   (%\"'&<>^| =)\r\n"
"CHAR1-CHAR2   all characters from CHAR1 to CHAR2 in ascending order\r\n"
"[CHAR1-CHAR2] same as CHAR1-CHAR2, if both Set1 and Set2 use this\r\n"
"[CHAR*]       in Set2, copies of CHAR until length of Set1\r\n"
"[CHAR*REPEAT] REPEAT copies of CHAR, REPEAT octal if starting with 0\r\n"
"[:alnum:]     all letters and digits\r\n"
"[:alpha:]     all letters\r\n"
"[:blank:]     all horizontal whitespace\r\n"
"[:cntrl:]     all control characters\r\n"
"[:digit:]     all digits\r\n"
"[:graph:]     all printable characters, not including space\r\n"
"[:lower:]     all lower case letters\r\n"
"[:print:]     all printable characters, including space\r\n"
"[:punct:]     all punctuation characters\r\n"
"[:space:]     all horizontal or vertical whitespace\r\n"
"[:upper:]     all upper case letters\r\n"
"[:xdigit:]    all hexadecimal digits\r\n"
"\r\n"
"Translation occurs if -d is not given and both Set1 and Set2 appear.\r\n"
"Set2 is extended to length of Set1 by repeating its last character.\r\n"
"Excess characters of Set2 are ignored. -s uses Set1 if not translating\r\n"
"nor deleting; else squeezing uses Set2 and occurs after translation or\r\n"
"deletion.\r\n"
;

static const WCHAR *finp = NULL, *fout = NULL;

static int f_inp( const WCHAR *s )
{
   if ( !s || !*s ) return Opt::Err_Missed_Value;
   finp = s;
   return 0;
}
static int f_out( const WCHAR *s )
{
   if ( !s || !*s ) return Opt::Err_Missed_Value;
   fout = s;
   return 0;
}

static Opt::option o_tr[] =
{ { 'c', "complement",      &c_opt, NULL  }
, { 'd', "delete",          &d_opt, NULL  }
, { 's', "squeeze-repeats", &s_opt, NULL  }
, { 'i', "input",           NULL,   f_inp }
, { 'o', "output",          NULL,   f_out }
, { 't', "truncate-set1",   &x_opt, NULL  }
};

extern int
tr(WCHAR **args,int na, const WCHAR *fi,const WCHAR *fo, bool,bool,bool,bool);

static int do_tr( WCHAR **args, int na )
{
   return tr(args, na, finp,fout, c_opt>0, d_opt>0, s_opt>0, x_opt>0);
}

//---------------------------------------------------------------------------------------

static const char h_cmp[] = "[Options] File1 [File2 [Skip1 [Skip2]]]\0"
"Compare two files byte by byte\0"
"Options:\r\n"
"  -b, --print-bytes Print differing bytes\r\n"
"  -i Skip1[:Skip2]  Skip the first Skip1 (or Skip2,Skip2) bytes of input files\r\n"
"  -l, --verbose     Output byte numbers and values of all differing bytes\r\n"
"  -n Limit          Compare at most Limit bytes\r\n"
"  -s, --quiet       Output nothing; yield exit status only\r\n"
"\1c"
"  \1e File1 File2\r\n"
"  \1e -lb -n 1024 File1 File2 10K 20K\r\n"
"\r\n"
"Skip1 and Skip2 are the number of bytes to skip in each file. Values may be\r\n"
"followed by the following multiplicative suffixes:\r\n"
"k 1000, K 1024, MB 1000*1000, M 1024*1024, GB 1000**3, G 1024**3\r\n"
"If a File is '-' or missing, read standard input\r\n"
;

#define f_skip  f_inp
#define skip    finp
#define f_limit f_out
#define limit   fout

static Opt::option o_cmp[] =
{ { 'b', "print-bytes",    &p_opt, NULL    }
, { 'i', "ignore-initial", NULL,   f_skip  }
, { 'l', "verbose",        &v_opt, NULL    }
, { 'n', "bytes",          NULL,   f_limit }
, { 's', "quiet",          &s_opt, NULL    }
, { 's', "silent",         &s_opt, NULL    }
};

extern int
cmp(WCHAR **args,int na, const WCHAR *skip,const WCHAR *limit, int verbose, bool print);

static int do_cmp( WCHAR **args, int na )
{
   return cmp(args, na, skip, limit, v_opt-s_opt, p_opt > 0);
}

//---------------------------------------------------------------------------------------

static const char h_basename[] = "{--help|--version|path [suffix]}\0"
"Prints file base name with possible removed suffix\0";

static const char h_dirname[] = "{--help|--version|path}\0"
"Prints file directory name\0";

static const char h_pathname[] = "[Options] Path ...\0"
"Prints specified parts of the full path name\0"
"Options:\r\n"
"  -r, --real        Try to resolve symbolic links\r\n"
"  -f, --full        Full path name (default mode)\r\n"
"  -d, --drive       Drive or Share name\r\n"
"  -p, --path        Path (no drive, no file name)\r\n"
"  -n, --name        Name of the file without extension\r\n"
"  -x, --ext         Extension of the file name (with '.')\r\n"
"  -v, --verbose     shows Original, Full and Real pathnames\r\n"
"  -a, --attr        file Attributes\r\n"
"  -t, --time        file update date-Time YYYY.MM.DD-HH:MI:SS,fffffff\r\n"
"  -c, --ctime       file Creation date-Time\r\n"
"  -l, --atime       file Last Access date-Time\r\n"
"  -z, --size        file siZe\r\n"
"  -s, --short       Short file name (modified --full mode)\r\n"
"\1c"
"  \1e File1 File2 Dir1\r\n"
"  \1e -nx Path1 Path2\r\n"
"\r\n"
"--full/short (-f/s) implies -dpnx options\r\n"
;

static Opt::option o_pathname[] =
{ { 'r', "real",    &r_opt, NULL }
, { 'f', "full",    &f_opt, NULL }
, { 'd', "drive",   &d_opt, NULL }
, { 'p', "path",    &p_opt, NULL }
, { 'n', "name",    &n_opt, NULL }
, { 'x', "ext",     &x_opt, NULL }
, { 'v', "verbose", &v_opt, NULL }
, { 'a', "attr",    &a_opt, NULL }
, { 't', "time",    &m_opt, NULL }
, { 'c', "ctime",   &c_opt, NULL }
, { 'l', "atime",   &l_opt, NULL }
, { 'z', "size",    &e_opt, NULL }
, { 's', "short",   &s_opt, NULL }
};

extern int pathname(char mode, WCHAR **args,int na, int options);

static int do_basename( WCHAR **args, int na )
{ return pathname('b', args, na, 0); }

static int do_dirname( WCHAR **args, int na )
{ return pathname('d', args, na, 0); }

static int do_pathname( WCHAR **args, int na )
{
   int o = 0;
   f_opt += s_opt;
   f_opt += (int)!(d_opt ||p_opt ||n_opt ||x_opt ||m_opt||c_opt||l_opt ||e_opt ||a_opt);
   if (d_opt > 0 || f_opt > 0) o |= (1 << ('d'-'a'));
   if (p_opt > 0 || f_opt > 0) o |= (1 << ('p'-'a'));
   if (n_opt > 0 || f_opt > 0) o |= (1 << ('n'-'a'));
   if (x_opt > 0 || f_opt > 0) o |= (1 << ('x'-'a'));
   if (r_opt > 0             ) o |= (1 << ('r'-'a'));
   if (v_opt > 0             ) o |= (1 << ('v'-'a')); // special case
   if (m_opt > 0             ) o |= (1 << ('t'-'a'));
   if (c_opt > 0             ) o |= (1 << ('c'-'a')) | (1 << ('t'-'a'));
   if (l_opt > 0             ) o |= (1 << ('l'-'a')) | (1 << ('t'-'a'));
   if (e_opt > 0             ) o |= (1 << ('z'-'a'));
   if (s_opt > 0             ) o |= (1 << ('s'-'a'));
   if (a_opt > 0             ) o |= (1 << ('a'-'a'));
   return pathname('p', args, na, o);
}

static const char h_pwd[] = "[Options]\0"
   "Print the full filename of the current working directory.\0"
   "Options:\r\n"
   "  -L, --logical     use PWD from environment, even if it contains symlinks\r\n"
   "  -P, --physical    try to avoid all symlinks\r\n"
   "\1c"
   "  \1e -P"
;

static Opt::option o_pwd[] =
{ { 'L', "logical",  &l_opt, NULL }
, { 'P', "physical", &p_opt, NULL }
};

static int do_pwd( WCHAR **args, int na )
{
   WCHAR cdir[MAX_PATH];
   if (!::GetCurrentDirectoryW(MAX_PATH,cdir)) { cdir[0]=L'.'; cdir[1]=L'\0'; }
   args[0] = cdir; na = 1;
   int opt =
      (1 << ('d'-'a')) | (1 << ('p'-'a')) | (1 << ('n'-'a')) | (1 << ('x'-'a'))
   ;
   if (p_opt > l_opt) opt |= (1 << ('r' - 'a'));
   return pathname('p', args, na, opt);
}

//---------------------------------------------------------------------------------------

static const char h_cat[] = "[Options] File [...]\0"
"Concatenate File(s), or standard input, to standard output.\0"
"Options:\r\n"
"  -A, --show-all    equivalent to -vET\r\n"
"  -b, --number-nonblank  number nonblank output lines\r\n"
"  -e                equivalent to -vE\r\n"
"  -E, --show-ends   display $ at end of each line\r\n"
"  -n, --number      number all output lines\r\n"
"  -s, --squeeze-blank  never more than one single blank line\r\n"
"  -t                equivalent to -vT\r\n"
"  -T, --show-tabs   display TAB characters as ^I\r\n"
//"  -u                (ignored)\r\n"
"  -v --show-nonprinting  use ^ notation, except for CR, LF and TAB\r\n"
"\1c"
"  \1e File1 - File2 > File3\r\n"
"\r\n"
"If a File is '-' or missing, read standard input\r\n"
;

#define b_opt c_opt
#define t_opt i_opt
#define T_opt x_opt

static Opt::option o_cat[] =
{ { 'A', "show-all",        &a_opt, NULL }
, { 'b', "number-nonblank", &b_opt, NULL }
, { 'e', NULL,              &e_opt, NULL }
, { 'E', "show-ends",       &E_opt, NULL }
, { 'n', "number",          &n_opt, NULL }
, { 's', "squeeze-blank",   &s_opt, NULL }
, { 't', NULL,              &t_opt, NULL }
, { 'T', "show-tabs",       &T_opt, NULL }
, { 'u', NULL,              NULL,   NULL }
, { 'v', "show-nonprinting",&v_opt, NULL }
};

extern int
cat(WCHAR **args,int na, int num, bool nonpr, bool tab, bool ends, bool squeeze);

static int do_cat( WCHAR **args, int na )
{
   if (a_opt) { ++v_opt; ++E_opt; ++T_opt; }
   if (e_opt) { ++v_opt; ++E_opt; }
   if (t_opt) { ++v_opt; ++T_opt; }
   return cat(args, na,
      b_opt > 0 ? -1 : (n_opt > 0 ? +1 : 0),
      v_opt > 0, T_opt > 0, E_opt > 0, s_opt > 0
   );
}

//---------------------------------------------------------------------------------------

static const char h_streams[] = "[Options] File_or_Dir [...]\0"
"Enumerate alternate NTFS data stream(s), or standard input, to standard output.\0"
"Options:\r\n"
"  -s                recurse subdirectories (without symlinks)\r\n"
"  -S                recurse subdirectories and symbolic links\r\n"
"  -d                delete all alternate streams\r\n"
"  -D StreamName     delete specified stream\r\n"
"  --scan=Method     stream scan method: (A)uto, (N)tQuery or (B)ackupRead\r\n"
"  -z                skip \"Zone.Identifier\"\r\n"
"  -f                skip \"favicon\"\r\n"
"  -e                skip \"encryptable\"\r\n"
"  --stat            output search statistics\r\n"
;

#define S_opt       a_opt
#define z_opt       x_opt
#define f_stream    f_out
#define stream_name fout
#define f_scan      f_inp
#define scan_type   finp

static Opt::option o_streams[] =
{ { 's',  NULL,   &s_opt, NULL     }
, { 'S',  NULL,   &S_opt, NULL     }
, { 'd',  NULL,   &d_opt, NULL     }
, { 'D',  NULL,   NULL,   f_stream }
, { '\0', "scan", NULL,   f_scan   }
, { 'z',  NULL,   &z_opt, NULL     }
, { 'f',  NULL,   &f_opt, NULL     }
, { 'e',  NULL,   &e_opt, NULL     }
, { '\0', "stat", &v_opt, NULL     }
};

extern int
streams(
   char op, WCHAR **args,int na,
   int recurse, int skip, const WCHAR *sname, char method
);

static int do_streams( WCHAR **args, int na )
{
   char method = '\0';
   if ( scan_type ) {
      if (scan_type[0] == L'n' || scan_type[0] == L'N') method = 'n';
      if (scan_type[0] == L'b' || scan_type[0] == L'B') method = 'b';
   }

   int skip_stat = (z_opt > 0 ? 1 : 0) | (f_opt > 0 ? 2 : 0) | (e_opt > 0 ? 4 : 0);
   if (v_opt > 0) skip_stat |= 0x10000;

   return streams(
      d_opt > 0 || NULL != stream_name ? 'd' : 'e',
      args, na,
      S_opt > 0 ? +1 : (s_opt > 0 ? -1 : 0),
      skip_stat,
      stream_name,
      method
   );
}

//=======================================================================================

struct prog_info
{
   const char  *prog;
   const char  *help;
   Opt::option *options;
   int         n_opts;
   int (*doer)(WCHAR **args, int na);
};
#define PROG_ENTRY(nm) #nm,h_##nm,o_##nm,idim(o_##nm),do_##nm
#define PROG_ENTR0(nm) #nm,h_##nm,NULL,  0,           do_##nm

extern const prog_info modes[] =
{ { PROG_ENTRY( gnu     ) }
, { PROG_ENTR0( true    ) }
, { PROG_ENTR0( false   ) }
, { PROG_ENTRY( echo    ) }

, { PROG_ENTRY( mkdir   ) }
, { PROG_ENTRY( cp      ) }
, { PROG_ENTRY( mv      ) }
, { PROG_ENTRY( rm      ) }
, { PROG_ENTRY( touch   ) }

, { PROG_ENTRY( uuidgen ) }
, { PROG_ENTRY( sleep   ) }
, { PROG_ENTRY( who     ) }
, { PROG_ENTRY( uptime  ) }

, { PROG_ENTRY( tr      ) }
, { PROG_ENTRY( cmp     ) }

, { PROG_ENTRY( ln      ) }
, { PROG_ENTR0( basename) }
, { PROG_ENTR0( dirname ) }
, { PROG_ENTRY( pathname) }
, { PROG_ENTRY( pwd     ) }
, { PROG_ENTRY( streams ) }
, { PROG_ENTRY( cat     ) }
};

void version()
{ Con::o(Win::vers(modes[mode].prog)); }

//---------------------------------------------------------------------------------------

static void us_1( const char *ps, const char *prog )
{
   while ( ps && *ps )
   {
      const char *p1 = ps; while (*p1 && *p1 != '\1') ++p1;
      if ( !*p1 ) { Con::o(ps); break; }
      Con::o(ps, (int)(size_t)(p1-ps));
      switch (p1[1]) {
         case 'e': Con::f("%s.exe", prog);  break;
         case 'c': Con::o(common_opt_help); break;
         case 'm':
            int nc = 1;
            for (int i = 1; i < idim(modes); ++i) {
               if (nc + 1 + ::lstrlenA(modes[i].prog) > 80)
               { Con::n(); Con::o(' '); nc = 1; }
               if ( i > 1 ) Con::o(' ');
               Con::o(modes[i].prog); nc += 1 + ::lstrlenA(modes[i].prog);
            }
         break;
      }
      ps = p1 + 2;
   }
}

void usage()
{
   const char *ps = modes[mode].help, *prog = modes[mode].prog;

   Con::f("Usage:\r\n  %s.exe ", prog);
   us_1(ps, prog); Con::n(2);

   ps += ::lstrlenA(ps) + 1;
   us_1(ps, prog); Con::n();

   ps += ::lstrlenA(ps) + 1;
   if ( *ps ) { Con::n(); us_1(ps, prog); }
}

static bool set_mode( char *prog )
{
   int n2 = (int)::lstrlenA(prog) - 2;
   char cc = '\0';
   bool ok = false;

   if (n2 > 0 && ((prog[n2] == '3' && prog[n2+1] == '2') | (prog[n2] == '6' && prog[n2+1] == '4')))
   { cc = prog[n2]; prog[n2] = '\0'; }

   for (int i = 0; i < idim(modes); ++i) {
      if ( 0 == ::lstrcmpA(prog, modes[i].prog) ) { mode = i; ok = true; break; }
   }

   if (cc) prog[n2] = cc;
   return ok;
}

//---------------------------------------------------------------------------------------

int err_ret( int errcode, const char *msg, const WCHAR *name )
{
   WCHAR errtext[512];
   Win::geterr(errtext, idim(errtext));
   Con::err(true);
   Con::f("%s ERROR: %s", modes[mode].prog, msg);
   if ( name ) { Con::o(" \""); Con::o(name); Con::o('"'); }
   if ( errtext[0] ) { Con::f(L"\r\n  %s\r\n", errtext); }
   Con::err(false);
   return errcode;
}

//---------------------------------------------------------------------------------------

extern BOOL hard_link( WCHAR *from, WCHAR *to );
static bool up_privileges();

static int do_gnu( WCHAR **argv, int argc )
{
   WCHAR alias[512];

   ::lstrcpyW(alias, argv[0]);
   WCHAR *pw = alias + ::lstrlenW(argv[0]);
   while ( pw > alias && pw[-1] != L'\\' && pw[-1] != L'/' ) --pw;

   if ( l_opt > 0 )
   {
      up_privileges();
      for ( int i = 1; i < idim(modes); ++i )
      {
         Win::atow(modes[i].prog, pw, 20);
         ::lstrcpyW(pw + ::lstrlenA(modes[i].prog), L".exe");
         BOOL res = hard_link(argv[0], alias);
         if ( !res ) res = ::CopyFileW(argv[0], alias, FALSE);
         if ( !res ) return err_ret(-9, "Can't create gnu alias", alias);
         Con::o("Create alias \""); Con::o(alias); Con::o("\"\r\n");
      }
   }
   (void)argc;
   return 0;
}

//=======================================================================================

static bool up_privilege(HANDLE hToken, LPCTSTR pname)
{
   TOKEN_PRIVILEGES NewState={1}, SavedState;
   if (!::LookupPrivilegeValue(NULL, pname, &NewState.Privileges->Luid))
      return false;

   NewState.Privileges->Attributes = SE_PRIVILEGE_ENABLED;
   DWORD rlen = sizeof(SavedState);
   return (0 != ::AdjustTokenPrivileges(hToken,FALSE,&NewState,sizeof(NewState),&SavedState,&rlen)
      && ::GetLastError() == ERROR_SUCCESS
   );
}
static bool up_privileges()
{
#if 0
   typedef BOOL (APIENTRY *TFNCheckTokenMembership)(
      __in_opt HANDLE TokenHandle,
      __in     PSID SidToCheck,
      __out    PBOOL IsMember
   );
   union func {
      TFNCheckTokenMembership lpfnCheckTokenMembership;
      FARPROC pf;
   } u;

   // is User Administrator?
   u.pf = ::GetProcAddress(Win::hadv, "CheckTokenMembership");
   if (u.lpfnCheckTokenMembership) {
      SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
      PSID AdministratorsGroup;
      if (!::AllocateAndInitializeSid(
         &NtAuthority,2,SECURITY_BUILTIN_DOMAIN_RID,DOMAIN_ALIAS_RID_ADMINS,0,0,0,0,0,0,&AdministratorsGroup
      )) return false;

      BOOL IsMember = FALSE;
      if (!(*u.lpfnCheckTokenMembership)(NULL, AdministratorsGroup, &IsMember) || !IsMember)
         return false;
   }
#endif

   HANDLE hToken;
   if (!::OpenProcessToken(::GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken))
      return false;

   bool r1 = up_privilege(hToken, SE_BACKUP_NAME);
   bool r2 = up_privilege(hToken, SE_RESTORE_NAME);
   bool r3 = up_privilege(hToken, SE_TAKE_OWNERSHIP_NAME);
   bool r4 = up_privilege(hToken, SE_SECURITY_NAME);
  #ifndef SE_CREATE_SYMBOLIC_LINK_NAME
  #define SE_CREATE_SYMBOLIC_LINK_NAME      TEXT("SeCreateSymbolicLinkPrivilege")
  #endif
   bool r5 = up_privilege(hToken, SE_CREATE_SYMBOLIC_LINK_NAME);

   ::CloseHandle(hToken);
   return r1 && r2 && r3 && r4 && r5;
}

//=======================================================================================

#pragma warning(disable : 4702) // unreachable code
int __stdcall Main (void)
{
   WCHAR *argv[Opt::MaxArgs];
   int rc, argc = idim(argv);
   char progname[16];

   Win::init();
   Con::init();
   Opt::getargs(argc, argv);

   progname[0] = '\0';
   Opt::getprog(argv[0], progname, (int)sizeof(progname), true);

   set_mode( progname );
   rc = Opt::getopts(argc,argv, modes[mode].options,modes[mode].n_opts, mode > eEcho ? 1 : -1);
   if ( rc < 0 || rc > argc ) goto e;

   if ( mode == eGnu )
   {
      if ( rc >= argc ) {
         if ( l_opt <= 0 ) { usage(); rc = 1; goto x; }
      }
      else {
         progname[0] = '\0';
         Win::wtoa(argv[rc], progname, (int)sizeof(progname));
         set_mode(progname);
         if ( mode == eGnu ) { usage(); rc = 2; goto x; }
         ++rc;
         rc = Opt::getopts(argc,argv,modes[mode].options,modes[mode].n_opts, mode>eEcho ? rc:-rc);
         if ( rc < 0 || rc > argc ) goto e;
      }
   }

   if ( mode == eGnu ) rc = 0; else up_privileges();
   rc = modes[mode].doer(argv + rc, argc-rc);

e: Con::setOut(NULL);
   Con::setInp(NULL);
   rc = ( rc < 0 ) ? -rc : 0;
x: Win::cleanup();

#ifndef _DEBUG
   ::ExitProcess(rc); // hangs on exit elsewhere
#endif
   return rc;
}
#pragma warning(default : 4702) // unreachable code

//---------------------------------------------------------------------------------------

#ifdef _DEBUG
int wmain( int argc, WCHAR **argv )
{
   (void)argc, argv; return Main();
}
#endif

//=======================================================================================
